﻿using System;
using System.Collections.Generic;
using System.Threading;
using NUnit.Framework;
using Rhino.Mocks;
using Smith.AMC.Configuration;
using Smith.AMC.Network;
using Smith.AMC.Operations;

namespace MemcachedClientTests
{
    [TestFixture]
    public class ServerNodeMonitorTest
    {

        [Test]
        public void MonitorShouldQuitWhenToldSoAsyncronously()
        {
            IMemcachedServerNode node = MockRepository.GenerateStub<IMemcachedServerNode>();
            List<IMemcachedServerNode> servers = new List<IMemcachedServerNode> { node };
            ServerNodeMonitorConfiguration config = new ServerNodeMonitorConfiguration();
            ServerNodeMonitor monitor = new ServerNodeMonitor(servers, config);

            monitor.Start();
            //Sleep for 5 secs to get the monitor working
            Thread.Sleep(5000);

            ManualResetEvent evt = new ManualResetEvent(false);
            monitor.BeginStop(ar =>
            {
                monitor.EndStop(ar);
                ManualResetEvent evtInner = (ManualResetEvent)ar.AsyncState;
                evtInner.Set();
            }, evt);

            evt.WaitOne();

        }

        [Test]
        public void MonitorShouldQuitWhenToldBlocking()
        {
            IMemcachedServerNode node = MockRepository.GenerateStub<IMemcachedServerNode>();
            List<IMemcachedServerNode> servers = new List<IMemcachedServerNode> { node };
            ServerNodeMonitorConfiguration config = new ServerNodeMonitorConfiguration();
            ServerNodeMonitor monitor = new ServerNodeMonitor(servers, config);

            monitor.Start();
            //Sleep for 5 secs to get the monitor working
            Thread.Sleep(5000);

            monitor.Stop();
        }

        [Test]
        public void MonitorShouldCheckHealthOfServerNodeWhenErrorCountGoesAboveThreshold()
        {
            IMemcachedServerNode node = MockRepository.GenerateStub<IMemcachedServerNode>();
            IClientSocket clientSocket = MockRepository.GenerateStub<IClientSocket>();
            ServerNodeMonitorConfiguration config = new ServerNodeMonitorConfiguration();
            config.CheckInterval = TimeSpan.FromSeconds(1);
            config.FailedThreshold = 1;

            node.Stub(n => n.HealthCheckSocket).Return(clientSocket);
            node.FailedCount = 10;
            
            IAsyncResult asyncResult = MockRepository.GenerateStub<IAsyncResult>();
            clientSocket.Stub(c => c.BeginWrite<int>(
                Arg<Operation>.Is.Anything,
                Arg<AsyncCallback>.Is.Anything,
                Arg<object>.Is.Anything)).WhenCalled(a =>
                {
                    AsyncCallback cb = (AsyncCallback)a.Arguments[1];
                    cb.Invoke(asyncResult);

                }).Return(asyncResult);
            List<IMemcachedServerNode> servers = new List<IMemcachedServerNode> { node };

            ServerNodeMonitor monitor = new ServerNodeMonitor(servers, config);

            monitor.Start();
            //Sleep for 5 secs to get the monitor working
            Thread.Sleep(5000);

            monitor.Stop();

            clientSocket.AssertWasCalled(c => c.EndWrite<int>(Arg<IAsyncResult>.Is.Anything));
            // If monitor does not get an error when calling memcached server, it will have its failedcount reset to 0
            Assert.That(node.FailedCount, Is.EqualTo(0));
        }

        [Test]
        public void MonitorShouldResetFailedCountToZeroIfItGetsAResponseFromTheNode()
        {
            IMemcachedServerNode node = MockRepository.GenerateStub<IMemcachedServerNode>();
            IClientSocket clientSocket = MockRepository.GenerateStub<IClientSocket>();

            ServerNodeMonitorConfiguration config = new ServerNodeMonitorConfiguration();
            config.CheckInterval = TimeSpan.FromSeconds(1);
            config.FailedThreshold = 1;
            node.Stub(n => n.HealthCheckSocket).Return(clientSocket);
            node.FailedCount = 10;

            IAsyncResult asyncResult = MockRepository.GenerateStub<IAsyncResult>();
            clientSocket.Stub(c => c.BeginWrite<int>(
                Arg<Operation>.Is.Anything,
                Arg<AsyncCallback>.Is.Anything,
                Arg<object>.Is.Anything)).WhenCalled(a =>
                {
                    AsyncCallback cb = (AsyncCallback)a.Arguments[1];
                    cb.Invoke(asyncResult);

                }).Return(asyncResult);
            List<IMemcachedServerNode> servers = new List<IMemcachedServerNode> { node };

            ServerNodeMonitor monitor = new ServerNodeMonitor(servers, config);

            monitor.Start();
            //Sleep for 5 secs to get the monitor working
            Thread.Sleep(5000);

            monitor.Stop();

            // If monitor does not get an error when calling memcached server, it will have its failedcount reset to 0
            Assert.That(node.FailedCount, Is.EqualTo(0));
        }

        [Test]
        public void MonitorShouldMarkNodeAsDeadIfAnExceptionOccursWhenCheckingTheNode()
        {
            IMemcachedServerNode node = MockRepository.GenerateStub<IMemcachedServerNode>();
            IClientSocket clientSocket = MockRepository.GenerateStub<IClientSocket>();

            node.Stub(n => n.HealthCheckSocket).Return(clientSocket);
            node.FailedCount = 10;
            node.IsDead = false;

            IAsyncResult asyncResult = MockRepository.GenerateStub<IAsyncResult>();
            clientSocket.Stub(c => c.BeginWrite<int>(
                Arg<Operation>.Is.Anything,
                Arg<AsyncCallback>.Is.Anything,
                Arg<object>.Is.Anything)).WhenCalled(a =>
                {
                    AsyncCallback cb = (AsyncCallback)a.Arguments[1];
                    cb.Invoke(asyncResult);

                }).Return(asyncResult);
            clientSocket.Stub(c => c.EndWrite<int>(Arg<IAsyncResult>.Is.Anything)).Throw(new Exception("Timeout error"));
            List<IMemcachedServerNode> servers = new List<IMemcachedServerNode> { node };

            ServerNodeMonitorConfiguration config = new ServerNodeMonitorConfiguration();
            config.CheckInterval = TimeSpan.FromSeconds(1);
            config.FailedThreshold = 1;
            ServerNodeMonitor monitor = new ServerNodeMonitor(servers, config);

            monitor.Start();
            
            // Sleep for 5 secs to get the monitor working
            Thread.Sleep(5000);

            monitor.Stop();

            // If monitor does get an error when calling memcached server, it will be marked as dead
            Assert.That(node.IsDead, Is.True);
        }

    }
}
